package com.koi.annotation.aspect;

import cn.hutool.core.collection.CollectionUtil;
import com.koi.common.anno.KoiMenuDataScope;
import com.koi.common.ex.koi.YUtil;
import com.koi.common.satoken.KoiLoginUser;
import com.koi.common.satoken.KoiLoginUtil;
import com.koi.common.utils.string.StringUtil;
import com.koi.model.vo.base.BaseVo;
import com.koi.plus.mapper.system.SysMenuMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author ･ᴗ･
 * @description [MenuDataScopeAspect-菜单数据范围注解]
 */
@Slf4j
@Order(7)
@Aspect
@Component
public class MenuDataScopeAspect {

    /**
     * 数据权限过滤关键字 ${params.deptDataScope}
     * 如果是实体类当条件有别名，xml则也使用别名
     * <!-- 数据范围过滤 -->
     * ${vo.params.deptDataScope}
     *
     * @author ･ᴗ･
     */
    public static final String DATA_SCOPE = "menuDataScope";

    @Resource
    private SysMenuMapper sysMenuMapper;

    /**
     * @description [为什么采用前置通知？因为需要在方法执行前，将Mybatis IN 的范围参数值进行注入]
     */
    @Before("@annotation(koiMenuDataScope)")
    public void doBefore(JoinPoint point, KoiMenuDataScope koiMenuDataScope) {
        clearDataScope(point);
        handleDataScope(point, koiMenuDataScope);
    }

    protected void handleDataScope(final JoinPoint joinPoint, KoiMenuDataScope koiMenuDataScope) {
        // 1、先从sa-token提供session缓存中获取当前的用户数据
        // KoiLoginUser koiLoginUser = StpUtil.getSession().getModel(SaTokenSessionConstants.LOGIN_USER, KoiLoginUser.class);
        KoiLoginUser koiLoginUser = KoiLoginUtil.getLoginUser();
        if (ObjectUtils.isNotEmpty(koiLoginUser)) {
            // 2、如果是超级管理员 或者 角色为超级管理员(所有部门数据)，则不过滤数据，否则需要过滤数据
            if (!KoiLoginUtil.getIsKoiAdmin()) {
                List<Long> menuList = sysMenuMapper.listMenuIdData(koiLoginUser.getUserId());
                dataScopeFilter(joinPoint, menuList, koiMenuDataScope.menuAlias(), koiMenuDataScope.fieldName());
            }
        } else {
            YUtil.isTrue("请先进行登录");
        }
    }

    /**
     * @param joinPoint 切点
     * @param menuList  菜单权限
     * @description [数据范围过滤(必须携带请求参数并包含params，否则无法拼接条件)(必须需要extends类BaseVo)]
     * @author ･ᴗ･
     */
    public static void dataScopeFilter(JoinPoint joinPoint, List<Long> menuList, String menuAlias, String fieldName) {
        StringBuilder sqlString = new StringBuilder();
        String menuListString = null;
        if (CollectionUtil.isNotEmpty(menuList)) {
            menuListString = StringUtils.join(menuList, ",");
        } else { // 如果查询不到数据
            menuListString = "-666";
        }

        // 过滤字段名称 AND {}.dept_id IN ( {} )
        sqlString.append(StringUtil.format(" AND {}." + fieldName + " IN ( {} ) ", menuAlias, menuListString));
        log.info("菜单权限数据：{}，拼接SQL：{}", menuListString, sqlString);
        if (StringUtils.isNotBlank(sqlString.toString())) {
            Object params = joinPoint.getArgs()[0];
            if (ObjectUtils.isNotEmpty(params) && params instanceof BaseVo) {
                BaseVo baseVo = (BaseVo) params;
                baseVo.getParams().put(DATA_SCOPE, String.valueOf(sqlString));
            }
        }
    }

    /**
     * @description [如果params.deptDataScope有数据，拼接权限SQL前先清空params.deptDataScope，防止参数SQL注入]
     */
    private void clearDataScope(final JoinPoint joinPoint) {
        // 获取第一个传递参数，判断是否有传递参数，JoinPoint用于除了@Around其他都使用，ProceedingJoinPoint用于@Around
        Object[] args = joinPoint.getArgs();
        if (args.length > 0) {
            Object params = args[0];
            if (ObjectUtils.isNotEmpty(params) && params instanceof BaseVo baseVo) {
                Map<String, Object> paramsMap = baseVo.getParams();
                if (paramsMap == null) {
                    paramsMap = new HashMap<>();
                    baseVo.setParams(paramsMap);
                }
                paramsMap.put(DATA_SCOPE, "");
            }
        }
    }

}
